package com.eerl.pdev.terminal.cmd;

import com.eerl.pdev.util.EToolPluginConsoleToolWindowFactory;
import com.eerl.pdev.util.Notice;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.project.Project;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import lombok.Getter;
import lombok.ToString;

import java.io.*;

@Service(Service.Level.PROJECT)
@ToString
public final class Terminal {
    @Getter
    private long createTime;

    // some enum ==================================================
    private static Terminal INSTANCE = null;

    private boolean isInit;

    private static final String HOST = "127.0.0.1";
    private static final int SSH_PORT = 22;

    // 保活时长
    private final int CONNECT_ALIVE_MAX_TIMEOUT = 30 * 1000;
    private final int SESSION_CONNECT_WAITING_TIMEOUT = 1000;
    private final int CHANNEL_CONNECT_WAITING_TIMEOUT = 1000;
    // ssh链接存活时间
    public static final int SSH_SHORT_ALIVE_TIMEOUT_MS = 10 * 60 * 1000;
    public static final int SSH_LONG_ALIVE_TIMEOUT_MS = 30 * 60 * 1000;
    public static final int SSH_MAX_ALIVE_TIMEOUT_MS = 3 * 60 * 60 * 1000;
    public static final int SSH_SHORT_ALIVE_CHECK_TIME = 60 * 1000;
    public static final int SSH_LONG_ALIVE_CHECK_TIME = 3 * 60 * 1000;
    public static final int SSH_MAX_ALIVE_CHECK_TIME = 20 * 60 * 1000;
    private Session session;
    private JSch jsch;


    // global vars ==================================================
    private int sshAliveTimeoutMs = SSH_SHORT_ALIVE_TIMEOUT_MS;
    private int sshAliveCheckTime = SSH_SHORT_ALIVE_CHECK_TIME;

    @Getter
    private BufferedReader reader;
    private ChannelShell channel;
    private PrintStream commandStream;

    private Thread outThread;
    private boolean outThreadRunning;
    @Getter
    private long lastExecCommandUnixtime;

    private Project project;

    public Terminal(Project project) throws JSchException, IOException {
        init(project);
    }

    private void init(Project project) throws JSchException, IOException {
        // 咋滴?????
        this.project = project;
        final String password = "123456";
        final String user = "root";
        jsch = new JSch();
        session = jsch.getSession(user, HOST, SSH_PORT);
        session.setPassword(password);
        session.setConfig("StrictHostKeyChecking", "no");
        session.setServerAliveInterval(CONNECT_ALIVE_MAX_TIMEOUT);
        session.connect(SESSION_CONNECT_WAITING_TIMEOUT);

        channel = (ChannelShell) session.openChannel("shell");

        PipedOutputStream commandOutput = new PipedOutputStream();
        commandStream = new PrintStream(commandOutput);

        PipedInputStream pipeIn = new PipedInputStream(commandOutput);
        channel.setInputStream(pipeIn);

        InputStream in = channel.getInputStream();
        reader = new BufferedReader(new InputStreamReader(in));

        channel.connect(CHANNEL_CONNECT_WAITING_TIMEOUT);

        initTerminalOut();

        outThread.start();
        outThreadRunning = true;
        TerminalTimer terminalTimer = new TerminalTimer(project);
        terminalTimer.startTimer(this);

        createTime = System.currentTimeMillis();
        isInit = true;
    }

    private void initTerminalOut() {
        // 控制台输出
        outThread = new Thread(() -> {
            BufferedReader reader = this.getReader();
            String line;
            while (outThreadRunning && !Thread.currentThread().isInterrupted()) {
                try {
                    if ((line = reader.readLine()) == null) break;
                } catch (IOException ee) {
                    throw new RuntimeException(ee);
                }
                EToolPluginConsoleToolWindowFactory.printToConsole(project, line);
            }
        });
    }

    public void executeCommand(String command) {
        enableSSHAlive();
        commandStream.println(command);
        commandStream.flush();
        lastExecCommandUnixtime = System.currentTimeMillis();
    }

    public static synchronized Terminal getInstance(Project project) {
        Terminal service = project.getService(Terminal.class);
        if (!service.isInit) {
            try {
                service.init(project);
            } catch (JSchException | IOException e) {
                Notice.showNotification(project, e.getMessage(), NotificationType.ERROR);
                Notice.showNotification(project, e.getLocalizedMessage(), NotificationType.ERROR);
            }
        }
        return service;
    }

    public void close() {
        close("normal");
    }
    public synchronized void close(String reason) {
        EToolPluginConsoleToolWindowFactory.printToConsole(
            project,
            "[ " + System.currentTimeMillis() + " ] ssh connect timeout, [ " + reason + " ] disconnect! "
        );
        if (channel != null) channel.disconnect();
        try {
            outThreadRunning = false;
            outThread.interrupt();
            if (reader != null) reader.close();
            if (channel != null && !channel.isClosed()) channel.disconnect();
            if (session != null) session.disconnect();
            if (commandStream != null) commandStream.close();
            jsch = null;
            INSTANCE = null;
            isInit = false;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void timeUpdate() {
        if (sshAliveTimeoutMs == SSH_SHORT_ALIVE_TIMEOUT_MS) {
            sshAliveTimeoutMs = SSH_LONG_ALIVE_TIMEOUT_MS;
            sshAliveCheckTime = SSH_LONG_ALIVE_CHECK_TIME;
        } else if (sshAliveTimeoutMs == SSH_LONG_ALIVE_TIMEOUT_MS) {
            sshAliveTimeoutMs = SSH_MAX_ALIVE_TIMEOUT_MS;
            sshAliveCheckTime = SSH_MAX_ALIVE_CHECK_TIME;
        }
    }

    public boolean checkSSHAlive() {
        return this.session.isConnected();
    }
    private void enableSSHAlive() {
        if (!checkSSHAlive()) {
            close("SSH Dead");
            try {
                init(project);
            } catch (JSchException | IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public int getSShAliveCheckTime() {
        return sshAliveCheckTime;
    }

    public int getSShMaxAliveTimeoutMs() {
        return sshAliveTimeoutMs;
    }
}
